package com.bawei.es.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bawei.common.core.constant.EsConstant;
import com.bawei.common.core.domain.R;
import com.bawei.common.core.exception.ServiceException;
import com.bawei.common.core.utils.StringUtils;
import com.bawei.es.service.EsDocService;
import com.bawei.es.service.EsIndexService;
import com.bawei.system.domain.vo.EsDocInsertVo;
import com.bawei.system.domain.vo.EsDocQueryVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class EsDocServiceImpl implements EsDocService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    @Autowired
    private EsIndexService esIndexService;

    @Override
    public R<Boolean> batchInsertDocs(List<EsDocInsertVo> esDocInsertVo) {
        BulkRequest request = new BulkRequest();
        esDocInsertVo.forEach(e -> {
            IndexRequest source = new IndexRequest(e.getIndexName()).source(JSON.toJSONString(e.getData()), XContentType.JSON);
            if (StringUtils.isNotEmpty(e.getId())) {
                source.id(e.getId());
            }
            request.add(source);
        });
        try {
            BulkResponse bulk = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            return R.ok(true, "插入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.fail(false, "插入失败");
    }

    @Override
    public R queryDocs(EsDocQueryVo esDocInsertVo) {
        SearchRequest request = new SearchRequest(EsConstant.INDEX_NAME);
        //构建查询参数
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());
        if (StringUtils.isNotEmpty(esDocInsertVo.getKeyWord())) {
            builder.query(QueryBuilders.matchQuery("productName", esDocInsertVo.getKeyWord()));
        }
        //构建分页参数
        builder.from((esDocInsertVo.getPageVo().getCurrentPage() - 1) * esDocInsertVo.getPageVo().getPageSize());
        builder.size(esDocInsertVo.getPageVo().getPageSize());
        //构建排序参数
        try {
            SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            log.info("查询结果：{}", JSONObject.toJSONString(search));
            SearchHit[] hits = search.getHits().getHits();
            List<Map<String,Object>> resultData = new ArrayList<>(hits.length);
            Arrays.stream(hits).forEach(h->{
                Map<String, Object> sourceAsMap = h.getSourceAsMap();
                resultData.add(sourceAsMap);
            });
        } catch (IOException e) {
            log.error("根据条件查询es数据失败：{}", e);
        }
        return null;
    }

    @Override
    public R<Boolean> deleteDocsById(String docsId) {
        DeleteRequest deleteRequest = new DeleteRequest(EsConstant.INDEX_NAME, docsId);
        try {
            DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            return R.ok(true,"删除成功");
        } catch (IOException e) {
            throw new RuntimeException("删除失败");
        }
    }

    @Override
    public void batchDeleteDocs(List<String> docsIds) {
        BulkRequest bulkRequest = new BulkRequest();
        docsIds.forEach(docsId->{
            DeleteRequest deleteRequest = new DeleteRequest(EsConstant.INDEX_NAME, docsId);
            try {
                DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                throw new RuntimeException("删除失败");
            }
        });
    }

    @Override
    public void querySuggestions(EsDocQueryVo esDocQueryVo) {
        List<String> list = new ArrayList<>();

            SearchRequest searchRequest = new SearchRequest(EsConstant.INDEX_NAME);
            //自动补全查询
            searchRequest.source().suggest(new SuggestBuilder()
                    .addSuggestion("item_suggest", new CompletionSuggestionBuilder("suggestion")
                            .prefix(esDocQueryVo.getKeyWord())//查询的字段
                            .skipDuplicates(true)//是否去重
                            .size(10)//补全的词条数
                    ));

        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //处理结果
            CompletionSuggestion completionSuggestion =  searchResponse.getSuggest().getSuggestion("item_suggest");
            List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                list.add(text);
            }
        } catch (IOException e) {
            String message = e.getMessage();
            if (message.contains("not")){
                message="es索引下的关键字不存在";
            }
            throw new RuntimeException(e);
        }
        Suggest suggest = searchResponse.getSuggest();
        List<String> suggestList = new ArrayList<>();
        suggest.getSuggestion("suggestions").forEach(suggestion -> {
            suggestion.getOptions().forEach(option -> {
                suggestList.add(option.getText().toString());
            });
        });
    }

    public List<Map> searchSuggestion (EsDocQueryVo searchReq) {
        List<Map> productInfoResponseList = new ArrayList<>();

        if (StringUtils.isEmpty(searchReq.getKeyWord())){
            throw new ServiceException("搜索参数不可以为空");
        }

        String indexName = searchReq.getIndexName();
        //判断索引是否存在
        Boolean isExit = esIndexService.indexExit(indexName).getData();
        if (!isExit){
            throw new RuntimeException(indexName+"：该索引不存在");
        }
        SearchRequest searchRequest = new SearchRequest(indexName);
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        List<String> searchFields = searchReq.getSearchFields();
        // es多关键字搜索 创建一个和searchFields长度一样的数组(检索参数)
        String [] searchFieldArray= new String[searchFields.size()];
        for (int i = 0; i < searchFields.size(); i++) {
            searchFieldArray[i] = searchFields.get(i);
        }

        boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchReq.getKeyWord(), searchFieldArray));

        // 分页
        searchSourceBuilder.from((searchReq.getPageVo().getCurrentPage() - 1) * searchReq.getPageVo().getPageSize());
        searchSourceBuilder.size(searchReq.getPageVo().getPageSize());

        searchSourceBuilder.query(boolQueryBuilder);

        //高亮
        List<String> highLightFields = searchReq.getHighlightFields();
        HighlightBuilder highlightBuilder = new HighlightBuilder();

        //如果没有高亮字段 则默认所有字段为高亮
        if (CollectionUtils.isEmpty(highLightFields)){
            highLightFields = searchFields;
        }
        for (String highLightField : highLightFields) {
            highlightBuilder.field(highLightField);
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
        }

        searchSourceBuilder.highlighter(highlightBuilder);
        searchRequest.source(searchSourceBuilder);

        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits searchResponseHits = searchResponse.getHits();
            TotalHits totalHits = searchResponseHits.getTotalHits();
            // 若未查询到数量结果，直接进行返回
            if (totalHits.value != 0){
                searchReq.getPageVo().setTotalDocs(totalHits.value);
            }
            SearchHit[] searchHits = searchResponseHits.getHits();
            for (SearchHit searchHit : searchHits) {
                // 源数据
                String sourceAsString = searchHit.getSourceAsString();
                Map mapData = com.alibaba.fastjson2.JSONObject.parseObject(sourceAsString, Map.class);
                // 高亮
                Map<String, HighlightField> highlightFieldMap = searchHit.getHighlightFields();
                if (highlightFieldMap != null){
                    for (String highLightField : highLightFields) {
                        HighlightField highlightField = highlightFieldMap.get(highLightField);
                        if (highlightField != null){
                            // 拼装
                            Text[] fragments = highlightField.getFragments();
                            StringBuilder sb = new StringBuilder();
                            for (Text fragment : fragments) {
                                sb.append(fragment);
                            }
                            mapData.put(highLightField,sb.toString());
                        }
                    }
                }
                productInfoResponseList.add(mapData);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return productInfoResponseList;
    }
}
